package cz.ctu.guiproject.server.manager;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.logging.Logger;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import cz.ctu.guiproject.server.dispatcher.Dispatcher;
import cz.ctu.guiproject.server.messaging.PropagateUIUpdate;
import cz.ctu.guiproject.server.messaging.UIRequest;
import cz.ctu.guiproject.server.messaging.UIResponse;
import cz.ctu.guiproject.server.resources.Constants;
import cz.ctu.guiproject.server.resources.FileLoader;
import cz.ctu.guiproject.server.shared.Event;
import guiproject.server.view.View;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;

public class UIManager {

    private static final Logger logger = Logger.getLogger(UIManager.class.getName());
    private final Dispatcher dispatcher;
    private final LayoutManager layoutManager;

    public UIManager(Dispatcher dispatcher) {
        this.dispatcher = dispatcher;
        layoutManager = LayoutManagerImpl.getInstance();
    }

    public void initUI(String xml) {
        final UIRequest uiRequest = decodeUIRequest(xml);

        final UIResponse uiResponse = new UIResponse();
        // load layout file from xml
        final String layout = FileLoader.loadTextFile(Constants.LAYOUT_XML_PATH);

        final String uiResponseXML = encodeUIResponse(uiResponse, layout);
        dispatcher.send(uiRequest.getNetworkId(), uiResponseXML);
//        // send layout to LayoutManager as well (done automatically during server init)
//        layoutManager.init(layout);
    }

    private String encodeUIResponse(UIResponse uiResponse, String layout) {
        final StringWriter writer = new StringWriter();
        try {
            final XMLStreamWriter out = XMLOutputFactory.newInstance()
                    .createXMLStreamWriter(writer);

            out.writeStartDocument(Constants.CHARSET_UTF_8, Constants.VERSION_1_0);
            out.writeStartElement(Constants.CLIENT_UI_RESPONSE);
            out.writeEndElement();
            out.writeEndDocument();
            out.close();

        } catch (XMLStreamException | FactoryConfigurationError e) {
            logger.severe(e.getMessage());
        }
        final StringBuffer sb = writer.getBuffer();
        // TODO pass layout more elegantly!!
        final String template = sb.toString();
        final int half = template.indexOf(Constants.CLIENT_UI_RESPONSE) + Constants.CLIENT_UI_RESPONSE.length() + 1;
        final String first = template.substring(0, half);
        final String second = template.substring(half);
        final StringBuilder builder = new StringBuilder();
        builder.append(first);
        builder.append(layout);
        builder.append(second);
        return builder.toString();
    }

    private UIRequest decodeUIRequest(String xml) {
        // TODO get rid of [...] at the beginning
        final String nr = xml.substring(1, xml.indexOf(']'));
        final int networkId = Integer.parseInt(nr);
        xml = xml.substring(xml.indexOf(']') + 1);

        final SAXParserFactory factory = SAXParserFactory.newInstance();
        final UIRequestHandler handler = new UIRequestHandler();
        try {
            final SAXParser saxParser = factory.newSAXParser();
            saxParser.parse(new InputSource(new StringReader(xml)), handler);
        } catch (ParserConfigurationException | SAXException | IOException e) {
            logger.severe(e.getMessage());
        }
        final UIRequest uiRequest = handler.getUIRequest();
        uiRequest.setNetworkId(networkId);
        return uiRequest;
    }

    private class UIRequestHandler extends DefaultHandler {

        private UIRequest uiRequest;

        UIRequestHandler() {
            logger.info("UIRequestHandler called!");
            uiRequest = new UIRequest();
        }

        UIRequest getUIRequest() {
            return uiRequest;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes)
                throws SAXException {
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            // String chars = new String(ch).substring(start, start + length);
        }
    }

    public void propagateUIUpdate(String xml) {
        final PropagateUIUpdate propagateUIUpdate = decodePropagateUIUpdate(xml);
        // propagate UI update to other connected clients
        final View view = layoutManager.getView(propagateUIUpdate.getViewId());
        view.setAttribute(propagateUIUpdate.getAttributeName(), propagateUIUpdate.getAttributeValue(), true);
    }

    private PropagateUIUpdate decodePropagateUIUpdate(String xml) {
        // TODO get rid of [...] at the beginning
        final String nr = xml.substring(1, xml.indexOf(']'));
        final int networkId = Integer.parseInt(nr);
        xml = xml.substring(xml.indexOf(']') + 1);

        final SAXParserFactory factory = SAXParserFactory.newInstance();
        final PropagateUIUpdateHandler handler = new PropagateUIUpdateHandler();
        try {
            final SAXParser saxParser = factory.newSAXParser();
            saxParser.parse(new InputSource(new StringReader(xml)), handler);
        } catch (ParserConfigurationException | SAXException | IOException e) {
            logger.severe(e.getMessage());
        }
        final PropagateUIUpdate propagateUIUpdate = handler.getPropagateUIUpdate();
        propagateUIUpdate.setNetworkId(networkId);
        return propagateUIUpdate;
    }

    private class PropagateUIUpdateHandler extends DefaultHandler {

        private final String CLIENT_VIEW_ID = "viewId";
        private final String CLIENT_ATTR_NAME = "attribute-name";
        private final String CLIENT_ATTR_VALUE = "attribute-value";
        
        private boolean inViewId;
        private boolean inAttrName;
        private boolean inAttrValue;
        
        private PropagateUIUpdate propagateUIUpdate;

        PropagateUIUpdateHandler() {
            logger.info("PropagateUIUpdateHandler called!");
            propagateUIUpdate = new PropagateUIUpdate();
        }

        PropagateUIUpdate getPropagateUIUpdate() {
            return propagateUIUpdate;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes)
                throws SAXException {
            inViewId = qName.equals(CLIENT_VIEW_ID);
            inAttrName = qName.equals(CLIENT_ATTR_NAME);
            inAttrValue = qName.equals(CLIENT_ATTR_VALUE);
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
             String chars = new String(ch).substring(start, start + length);
             if(inViewId) {
                 propagateUIUpdate.setViewId(chars);
             } else if(inAttrName) {
                 propagateUIUpdate.setAttributeName(chars);
             } else if(inAttrValue) {
                 propagateUIUpdate.setAttributeValue(chars);
             }
        }
    }

    public void eventOccured(String xml) {
        final Event event = decodeEvent(xml);
        dispatcher.eventOccured(event);
    }

    private Event decodeEvent(String xml) {
        // TODO get rid of [...] at the beginning
        final String nr = xml.substring(1, xml.indexOf(']'));
        final int networkId = Integer.parseInt(nr);
        xml = xml.substring(xml.indexOf(']') + 1);

        final SAXParserFactory factory = SAXParserFactory.newInstance();
        final EventHandler handler = new EventHandler();
        try {
            final SAXParser saxParser = factory.newSAXParser();
            saxParser.parse(new InputSource(new StringReader(xml)), handler);
        } catch (ParserConfigurationException | SAXException | IOException e) {
            logger.severe(e.getMessage());
        }
        final Event event = handler.getEvent();
        event.setNetworkId(networkId);
        return event;
    }

    private class EventHandler extends DefaultHandler {

        private boolean inEventId;
        private boolean inEventKeys;
        private boolean inEventValues;
        private Event event;

        EventHandler() {
            logger.info("EventHandler called!");
            event = new Event();
        }

        Event getEvent() {
            return event;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes)
                throws SAXException {

            inEventId = qName.equals(Constants.CLIENT_EVENT_ID);
            inEventKeys = qName.equals(Constants.CLIENT_EVENT_KEYS);
            inEventValues = qName.equals(Constants.CLIENT_EVENT_VALUES);
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            String chars = new String(ch).substring(start, start + length);
            if (inEventId) {
                event.setEventId(chars);
            } else if (inEventKeys) {
                final List<String> keyList = new LinkedList<>();
                final StringTokenizer st = new StringTokenizer(chars, ",");
                while (st.hasMoreTokens()) {
                    keyList.add(st.nextToken());
                }
                final String[] keyArr = keyList.toArray(new String[0]);
                event.setKeys(keyArr);
            } else if (inEventValues) {
                final List<String> valueList = new LinkedList<>();
                final StringTokenizer st = new StringTokenizer(chars, ",");
                while (st.hasMoreTokens()) {
                    valueList.add(st.nextToken());
                }
                final String[] valueArr = valueList.toArray(new String[0]);
                event.setValues(valueArr);
            }
        }
    }
}